package top.lishuoboy.mybatis.mapper;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import top.lishuoboy.mybatis.vo.MyPage;

import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

/**
 * 万能 sql 执行器
 * 单表优先用BaseMapper<T>、BaseService<T>; 多表，DDL等用 SqlMapper
 * MP与mybatis中的不同是 select返回的是Long，不是Integer
 */
@Mapper
public interface SqlMapper {

    /** select 多条记录。返回 List<Map>。下划线未转驼峰。 */
    @Select("${sql}")
    <V> List<Map<String, V>> selectList(String sql);

    /** select 多条记录。返回 List<T>。下划线转驼峰。 */
    default <T> List<T> selectList(String sql, Class<T> tClass) {
        return BeanUtil.copyToList(this.selectList(sql), tClass, CopyOptions.create().ignoreCase());
    }

    /** select 一条记录（查到多条会报错）。返回 Map。下划线未转驼峰。 */
    @Select("${sql}")
    <V> Map<String, V> selectOne(String sql);

    /** select 一条记录（查到多条会报错）。返回 <T>。下划线转驼峰。 */
    default <T> T selectOne(String sql, Class<T> tClass) {
        return BeanUtil.toBean(this.selectOne(sql), tClass, CopyOptions.create().ignoreCase());
    }

    /** select 一列（查多列返回第一列）。返回 List<E>。 */
    @Select("${sql}")
    <E> List<E> selectOneColumn(String sql);

    /** select 一个值（查到多行会报错，查多列返回第一列）。select count() 返回值为 Long */
    @Select("${sql}")
    <E> E selectOneVal(String sql);

    /* 执行 insert update delete 和 DDL */
    @Update("${sql}")
    Integer executeUpdate(String sql);

    /** select 多条记录，分页。返回 List<Map>。下划线转驼峰。 */
    default <V> List<Map<String, V>> selectList(String sql, MyPage myPage) {
        transSqlOrders(sql, myPage);
        PageHelper.<Map<String, V>>startPage(myPage.toPageHelper()).setCount(myPage.isSearchCount()).setPageSizeZero(myPage.toPageSizeZero());
        return this.selectList(sql);
    }

    /** select 多条记录，分页。返回 List<T>。下划线转驼峰。 */
    default <T> List<T> selectList(String sql, MyPage myPage, Class<T> tClass) {
        transSqlOrders(sql, myPage);
        PageHelper.<T>startPage(myPage.toPageHelper()).setCount(myPage.isSearchCount()).setPageSizeZero(myPage.toPageSizeZero());
        return this.selectList(sql, tClass);
    }

    /** select 多条记录，分页。返回 MyPage<Map>。下划线转驼峰。 */
    default <V> MyPage<Map<String, V>> selectPage(String sql, MyPage myPage) {
        transSqlOrders(sql, myPage);
        Page<Map<String, V>> page = PageHelper.<Map<String, V>>startPage(myPage.toPageHelper()).setCount(myPage.isSearchCount()).setPageSizeZero(myPage.toPageSizeZero());
        List<Map<String, V>> mapList = this.selectList(sql);
        PageInfo<Map<String, V>> pageInfo = page.toPageInfo();
        return new MyPage<Map<String, V>>(myPage.getCurrent(), myPage.getSize(), myPage.getOrders()).setSearchCount(myPage.isSearchCount())
            .setRecords(mapList).setTotal(pageInfo.getTotal()).setPages(pageInfo.getPages());
    }

    /** select 多条记录，分页。返回 MyPage<T>。下划线转驼峰。 */
    default <T> MyPage<T> selectPage(String sql, MyPage myPage, Class<T> tClass) {
        transSqlOrders(sql, myPage);
        Page<T> page = PageHelper.<T>startPage(myPage.toPageHelper()).setCount(myPage.isSearchCount()).setPageSizeZero(myPage.toPageSizeZero());
        List<T> tList = this.selectList(sql, tClass);
        PageInfo<T> pageInfo = page.toPageInfo();
        return new MyPage<T>(myPage.getCurrent(), myPage.getSize(), myPage.getOrders()).setSearchCount(myPage.isSearchCount())
            .setRecords(tList).setTotal(pageInfo.getTotal()).setPages(pageInfo.getPages());
    }

    /**
     * 结合sql，将myPage中的orders字段加上表名前缀（优先取前面的表的），防止多个表中有重命的字段导致报错。
     * ### Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'prod_id' in order clause is ambiguous
     */
    default void transSqlOrders(String sql, MyPage myPage) {
        Map<String, String> sqlTableNameMap = getSqlTableName(sql);
        if (sqlTableNameMap.size() < 2 || StrUtil.isBlank(myPage.getOrders())) {
            return;
        }
        String dbName = selectOneVal("select database()");
        String orderBy = myPage.getOrders();
        List<String> orderByList = StrUtil.splitTrim(orderBy, StrPool.COMMA);

        for (Map.Entry<String, String> sqlTableName : sqlTableNameMap.entrySet()) {
            List<String> columnNameList = selectOneColumn("SELECT column_name FROM `information_schema`.`COLUMNS` WHERE TABLE_SCHEMA = '" + dbName + "' and table_name = '" + sqlTableName.getKey() + "'");
            for (int i = 0; i < orderByList.size(); i++) {
                String column = orderByList.get(i);
                if (column.contains(StrPool.DOT)) {
                    continue;
                }
                if (columnNameList.contains(StrUtil.subBefore(column, StrPool.C_SPACE, false))) {
                    if (StrUtil.isNotBlank(sqlTableName.getValue())) {
                        orderByList.set(i, sqlTableName.getValue() + StrPool.DOT + column);
                    } else {
                        orderByList.set(i, sqlTableName.getKey() + StrPool.DOT + column);
                    }
                }
            }
        }
        myPage.setOrders(StrUtil.replaceChars(orderByList.toString(), "[]", ""));
    }

    /** 解析出sql中所有的表名。Map 的 key为表全名，value为表名的别名（没有别名时为全名） */
    default Map<String, String> getSqlTableName(String sql) {
        sql = sql.toLowerCase();
        sql = StrUtil.subAfter(sql, " from ", false);
        sql = StrUtil.subBefore(sql, " where ", true);
        sql = StrUtil.subBefore(sql, " on ", true);
        List<String> tables = StrUtil.splitTrim(sql, ",");
        List<String> tableList = new ArrayList<>();  // 带别名的
        for (String table : tables) {
            List<String> strings = StrUtil.splitTrim(table, " join ");
            CollUtil.addAllIfNotContains(tableList, strings);
        }

        Map<String, String> tableMap = new IdentityHashMap<>();   // key可以重复的Map。解决一个sql同一个表自关联的问题
        for (int i = 0; i < tableList.size(); i++) {
            String table = tableList.get(i);
            if (table.startsWith("(")) {  // 子表
                String tableName = StrUtil.subBefore(table, ")", false) + ")";
                String tableAlias = StrUtil.subAfter(table, StrPool.C_SPACE, true);
                tableMap.put(tableName, tableAlias);
            } else {
                String tableName = StrUtil.subBefore(table, StrPool.C_SPACE, false);
                String tableAlias = StrUtil.subAfter(table, StrPool.C_SPACE, true);
                tableMap.put(tableName, tableAlias);
            }
        }
        return tableMap;
    }
}